package com.gusto.engine.recommend.prediction;

import java.util.List;
import java.util.Map;

import org.apache.log4j.Logger;

import com.gusto.engine.colfil.Distance;
import com.gusto.engine.colfil.Rating;
import com.gusto.engine.recommend.PredictionService;
import com.gusto.engine.recommend.prediction.base.BaseHybridImpl;

/**
 * <p>Switching algorithm, switches between collaborative and semantic algorithm.<br/>
 * With 3 strategies : 
 * <ul>
 *  <li>applies collaborative, and if it can't it applies semantic.
 *  <li>applies semantic, and if it can't it applies collaborative.
 *  <li>applies the algorithme with the best chances (collaborative or semantic).
 * </ul>
 * </p>
 * 
 * @author amokrane.belloui@gmail.com
 */
public class HybridSwitching extends BaseHybridImpl implements PredictionService {
	
	private Logger log = Logger.getLogger(getClass());
	
	protected Double doPrediction(long userId, long itemId, 
			List<Distance> userNeighborhoodCol,
			List<Distance> itemNeighborhoodCol,
			List<Distance> userNeighborhoodSem,
			List<Distance> itemNeighborhoodSem) {
		
		//return _doPredictionColSem(userId, itemId, userNeighborhoodCol, itemNeighborhoodCol, userNeighborhoodSem, itemNeighborhoodSem);
		//return _doPredictionSemCol(userId, itemId, userNeighborhoodCol, itemNeighborhoodCol, userNeighborhoodSem, itemNeighborhoodSem);
		return _doPredictionBestChances(userId, itemId, userNeighborhoodCol, itemNeighborhoodCol, userNeighborhoodSem, itemNeighborhoodSem);
		
	}
	
	protected Double _doPredictionColSem(long userId, long itemId, 
			List<Distance> userNeighborhoodCol,
			List<Distance> itemNeighborhoodCol,
			List<Distance> userNeighborhoodSem,
			List<Distance> itemNeighborhoodSem) {
		
		log.debug("Switching (Col -> Sem strategy)...");
		
		Map<Long, Double> usersWeights = this.buildUserWeights(userId, userNeighborhoodCol);
		Map<Long, Double> itemsWeights = this.buildItemWeights(itemId, itemNeighborhoodCol);
		
		List<Rating> evals = collaborativeService.getSubMatrix(usersWeights.keySet(), itemsWeights.keySet());
		
		Double val = null;
		
		double user_mean = collaborativeService.getUserMeanRating(userId);
		double item_mean = collaborativeService.getItemMeanRating(itemId);
		
		if (evals.size() >= params.getColMinEvals()) {
			val = this.buildPrediction(evals, user_mean, item_mean, usersWeights, itemsWeights);
			nbCollaborative ++;
		}
		else {
			
			Map<Long, Double> usersWeightsSem = this.buildUserWeights(userId, userNeighborhoodSem);
			Map<Long, Double> itemsWeightsSem = this.buildItemWeights(itemId, itemNeighborhoodSem);
			
			List<Rating> evalsSem = collaborativeService.getSubMatrix(usersWeightsSem.keySet(), itemsWeightsSem.keySet());
			
			if (evalsSem.size() >= params.getSemMinEvals()) {
				val = this.buildPrediction(evalsSem, user_mean, item_mean, usersWeightsSem, itemsWeightsSem);
				nbSemantic ++;
			}
			/*
			else {
				val = (user_mean + item_mean) / 2;
			}
			*/
		}
		return val;
	}
	
	
	protected Double _doPredictionSemCol(long userId, long itemId, 
			List<Distance> userNeighborhoodCol,
			List<Distance> itemNeighborhoodCol,
			List<Distance> userNeighborhoodSem,
			List<Distance> itemNeighborhoodSem) {
		
		log.debug("Switching (Sem -> Col strategy)...");
		
		Double val = null;
		
		Map<Long, Double> usersWeightsSem = this.buildUserWeights(userId, userNeighborhoodSem);
		Map<Long, Double> itemsWeightsSem = this.buildItemWeights(itemId, itemNeighborhoodSem);
		
		List<Rating> evalsSem = collaborativeService.getSubMatrix(usersWeightsSem.keySet(), itemsWeightsSem.keySet());
		
		double user_mean = collaborativeService.getUserMeanRating(userId);
		double item_mean = collaborativeService.getItemMeanRating(itemId);
		
		if (evalsSem.size() >= params.getSemMinEvals()) {
			val = this.buildPrediction(evalsSem, user_mean, item_mean, usersWeightsSem, itemsWeightsSem);
			nbSemantic ++;
		}
		else {
			Map<Long, Double> usersWeights = this.buildUserWeights(userId, userNeighborhoodCol);
			Map<Long, Double> itemsWeights = this.buildItemWeights(itemId, itemNeighborhoodCol);
			
			List<Rating> evals = collaborativeService.getSubMatrix(usersWeights.keySet(), itemsWeights.keySet());
			
			if (evals.size() >= params.getColMinEvals()) {
				val = this.buildPrediction(evals, user_mean, item_mean, usersWeights, itemsWeights);
				nbCollaborative ++;
			}
		}
		return val;
	}
	
	protected Double _doPredictionBestChances(long userId, long itemId, 
			List<Distance> userNeighborhoodCol,
			List<Distance> itemNeighborhoodCol,
			List<Distance> userNeighborhoodSem,
			List<Distance> itemNeighborhoodSem) {
		
		log.debug("Switching (Best chances strategy)...");
		
		// Collaborative
		Map<Long, Double> usersWeights = this.buildUserWeights(userId, userNeighborhoodCol);
		Map<Long, Double> itemsWeights = this.buildItemWeights(itemId, itemNeighborhoodCol);
		List<Rating> evals = collaborativeService.getSubMatrix(usersWeights.keySet(), itemsWeights.keySet());
		
		// Semantic
		Map<Long, Double> usersWeightsSem = this.buildUserWeights(userId, userNeighborhoodSem);
		Map<Long, Double> itemsWeightsSem = this.buildItemWeights(itemId, itemNeighborhoodSem);
		List<Rating> evalsSem = collaborativeService.getSubMatrix(usersWeightsSem.keySet(), itemsWeightsSem.keySet());
		
		// Prediction
		Double val = null;
		
		double user_mean = collaborativeService.getUserMeanRating(userId);
		double item_mean = collaborativeService.getItemMeanRating(itemId);
		
		Double valCollaborative = null;
		Double valSemantic = null;
		
		if (evals.size() >= params.getColMinEvals()) {
			valCollaborative = this.buildPrediction(evals, user_mean, item_mean, usersWeights, itemsWeights);
		}
		if (evalsSem.size() >= params.getSemMinEvals()) {
			valSemantic = this.buildPrediction(evalsSem, user_mean, item_mean, usersWeightsSem, itemsWeightsSem);
		}
		
		if ((valCollaborative != null) && (valSemantic != null)) {
			Integer qCollaborative = evals.size() - params.getColMinEvals();
			Integer qSemantic = evalsSem.size() - params.getSemMinEvals();
			// choose the algorithm with the best chances 
			if (qCollaborative.compareTo(qSemantic) >= 0) {
				val = valCollaborative;
				nbCollaborative ++;
			}
			else {
				val = valSemantic;
				nbSemantic ++;
			}
		}
		else {
			if (valCollaborative != null) {
				val = valCollaborative;
				nbCollaborative ++;
			}
			else if (valSemantic != null) {
				val = valSemantic;
				nbSemantic ++;
			}
		}
		return val;
	}
	
}
